// Copyright (c) 2011 Vratislav Cermak (vratacermak@gmail.com)
//  
// This file is part of SrtSync Project. See http://code.google.com/p/srtsync/ for more information.
//  
// SrtSync is an open source project. Using of SrtSync is free of charge.
// It is distributed under GNU General Public License version 3 (GPLv3).
// GPLv3 can be found at http://www.gnu.org/licenses/gpl.html.
//  
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE.  See the license for more details.

using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.IO;
using SrtSync.Core;
using SrtSync.Core.Framework;
using SrtSync.Core.Notifications;

namespace SrtSync.GtkWidgets
{
    [System.ComponentModel.ToolboxItem(true)]
    public class EditorWidget : Gtk.NodeView, IEditorView
    {
        public enum ZoomTypes
        {
            In,
            Out,
            Default
        }
        
        private readonly List<IObserver<EditorViewNotification>> _observers = new List<IObserver<EditorViewNotification>>();
        private IEditor _editor;
        private bool _skipInvokingChangedEvent;
        private int? _defaultFontSize;

        public event EventHandler<EventArgs> Changed;
        
        public enum TargetType 
        {
            Uri
        };
        
        private static Gtk.TargetEntry[] targetTypes = new Gtk.TargetEntry[]
        {
            new Gtk.TargetEntry ("text/uri-list", 0, (uint)TargetType.Uri)
        };
        
        public EditorWidget()
        {            
            NodeStore = new EntriesNodesStore();
            
            AppendColumn("Index", new Gtk.CellRendererText(), "text", 0);
            AppendColumn("Text", new Gtk.CellRendererText(), "text", 1);
            
            NodeSelection.Mode = Gtk.SelectionMode.Multiple;
            NodeSelection.Changed += delegate { FireChanged(); };
            
            Gtk.Drag.DestSet(this, Gtk.DestDefaults.Drop, targetTypes, Gdk.DragAction.Default);
        }
        
        protected override void OnStyleSet(Gtk.Style previousStyle)
        {
            if (!_defaultFontSize.HasValue)
            {
                _defaultFontSize = this.Style.FontDescription.Size;
            }
            
            base.OnStyleSet(previousStyle);
        }
        
        protected override void OnDragDataReceived(Gdk.DragContext context, int x, int y, Gtk.SelectionData selectionData, uint info, uint time)
        {
            if (selectionData.Length > 0 && selectionData.Format == 8)
            {
                Gtk.Drag.Finish(context, true, true, time);
                
                string data = Encoding.ASCII.GetString(selectionData.Data, 0, selectionData.Length);
                List<string> files = new List<string>();
                
                using (TextReader reader = new StringReader(data.Trim()))
                {
                    while (true)
                    {
                        string line = reader.ReadLine();
                        if (line == null)
                        {
                            break;
                        }
                        line = line.Trim('\0');
                        if (String.IsNullOrEmpty(line))
                        {
                           continue;
                        }
                        Uri uri = new Uri(line);
                        if (!uri.IsFile)
                        {
                            continue;
                        }
                        files.Add(uri.LocalPath);
                    }
                }
                
                if (files.Count > 0)
                {
                    // dispatching to itself
                    Dispatch(new EditorViewNotification { Path = files[0], Type = EditorViewNotificationTypes.Load });
                }
            }       
            else
            {
                base.OnDragDataReceived(context, x, y, selectionData, info, time);            
            }
        }
        
        public void Attach(IEditor editor)
        {
            _editor = editor;
            _editor.Register(this);
        }

        public new void SelectAll()
        {
            NodeSelection.SelectAll();
        }

        public void DeselectAll()
        {
            NodeSelection.UnselectAll();
        }

        public new IEnumerable<Entry> Selection
        {
            get { return NodeSelection.SelectedNodes.Select(x => ((EntryNode) x).Entry); }
        }

        public bool Empty
        {
            get { return _editor.Empty; }
        }

        public void Register(IObserver<EditorViewNotification> observer)
        {
            _observers.Add(observer);
        }

        public void UnRegister(IObserver<EditorViewNotification> observer)
        {
            _observers.Remove(observer);
        }
        
        public void Process(EditorModelNotification data)
        {
            data.Type.CheckValue();
            
            try
            {
                _skipInvokingChangedEvent = true;
                
                switch (data.Type)
                {
                case EditorModelNotificationTypes.Reload:
                    NodeStore.Clear();
                    if (!_editor.Empty)
                    {
                        foreach (Entry entry in _editor.Entries)
                        {
                            NodeStore.AddNode(new EntryNode(entry));
                        }
                    }
                    break;
                case EditorModelNotificationTypes.Remove:
                    ((EntriesNodesStore)NodeStore).RemoveNodes(data.Entries);
                    if (_editor.Entries.Count() > 0)
                    {
                        SelectIndex(data.Entries.First().Index, _editor.Entries.Last().Index);
                    }
                    break;
                case EditorModelNotificationTypes.Replace:
                    List<EntryNode > nodes = new List<EntryNode>();
                    int position = data.From.First().Index;
                    ((EntriesNodesStore)NodeStore).RemoveNodes(data.From);
                    foreach (Entry entry in data.To)
                    {
                        nodes.Add(new EntryNode(entry));
                        NodeStore.AddNode(nodes.Last(), (position++) - 1);
                    }
                    if (nodes.Count > 0)
                    {
                        SelectIndex(nodes.Last().Index);
                    }
                    else
                    {
                        if (data.From.Count() > 0)
                        {
                            SelectIndex(data.From.First().Index);
                        }
                    }
                    break;
                case EditorModelNotificationTypes.Modified:                
                    break;
                }
                
                if (SelectedIndex < 0 && !_editor.Empty && _editor.Entries.Count() > 0)
                {
                    SelectIndex(_editor.Entries.First().Index);
                }
            }
            finally
            {
                _skipInvokingChangedEvent = false;
            }
            
            FireChanged();
        }
        
        bool SelectIndex(params int[] index)
        {
            bool found = false;
            
            Model.Foreach(delegate(Gtk.TreeModel model, Gtk.TreePath path, Gtk.TreeIter iter) 
            {
                EntryNode node = (EntryNode)NodeStore.GetNode(path);
                if (index.Contains(node.Entry.Index))
                {
                    found = true;
                    
                    SetCursor(path, Columns[0], false);
                    
                    return true;
                }
                return false;
            });
            
            return found;
        }

        private void FireChanged()
        {
            if (_skipInvokingChangedEvent)
            {
                return;
            }
            
            EventHandler<EventArgs> handler = Changed;
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }

        public void Dispatch(EditorViewNotification data)
        {
            _observers.ForEach(x => x.Process(data));
        }
        
        public bool Modified
        {
            get { return _editor.Modified; }
        }

        public Encoding Encoding
        {
            get { return _editor.Encoding; }
        }
        
        public string Info
        {
            get 
            {
                return _editor.Empty ? "-" : _editor.Path;
            }
        }
        
        public int Count 
        { 
            get
            {
                return _editor.Empty ? 0 : _editor.Entries.Count();
            }
        }
        
        public void Zoom(ZoomTypes how)
        {
            Pango.FontDescription font = Style.FontDescription.Copy();
            switch (how)
            {
            case ZoomTypes.In:
                font.Size = (int)(font.Size * Pango.Scale.Large);
                break;
            case ZoomTypes.Out:
                font.Size = (int)(font.Size * Pango.Scale.Small);
                break;
            case ZoomTypes.Default:
                font.Size = _defaultFontSize.Value;
                break;
            default:
                throw new ArgumentOutOfRangeException();
            }
            ModifyFont(font);
        }
        
        public bool CanZoom(ZoomTypes how)
        {
            switch (how)
            {
            case ZoomTypes.In:
                return Style.FontDescription.Size <= _defaultFontSize * Pango.Scale.XXLarge;
            case ZoomTypes.Out:
                return Style.FontDescription.Size >= _defaultFontSize * Pango.Scale.XXSmall;
            case ZoomTypes.Default:
                return Style.FontDescription.Size != _defaultFontSize;
            default:
                throw new ArgumentOutOfRangeException();
            }
        }
        
        public int SelectedIndex
        {
            get 
            {
                if (_editor != null && !_editor.Empty)
                {
                    Gtk.TreePath path;
                    Gtk.TreeViewColumn column;
                    GetCursor(out path, out column);
                    
                    if (path != null)
                    {                    
                        EntryNode node = (EntryNode)NodeStore.GetNode(path);
                        if (node != null)
                        {
                            return node.Entry.Index;
                        }
                    }
                }
                
                return -1;
            }
            set 
            {
                if (value != SelectedIndex && _editor != null && !_editor.Empty && value != -1)
                {
                    SelectIndex(value);
                        
                    FireChanged();
                }
            }
        }
    }
}